﻿using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using NuSoft.ExpressionEditor.Common.Attributes;
using NuSoft.ExpressionEditor.Common.Collections;
using NuSoft.ExpressionEditor.Common.Helpers;
using NuSoft.ExpressionEditor.Common.Interfaces;

namespace NuSoft.ExpressionEditor
{
    /// <summary>
    /// Represents a Expression Editor control. 
    /// </summary>
    public class ExpressionEditor : Control
    {
        /// <summary>
        /// Identifies the <see cref="T:NuSoft.ExpressionEditor.ExpressionEditor.SupportedMethods"/> dependency property. 
        /// </summary>
        public static readonly DependencyProperty SupportedMethodsProperty =
            DependencyProperty.Register("SupportedMethods", typeof(SupportedMethodsCollection), typeof(ExpressionEditor), new PropertyMetadata(null,SupportedMethodsChanged));

        /// <summary>
        /// Identifies the <see cref="T:NuSoft.ExpressionEditor.ExpressionEditor.SourceObjType"/> dependency property. 
        /// </summary>
        public static readonly DependencyProperty SourceObjTypeProperty =
            DependencyProperty.Register("SourceObjType", typeof(Type), typeof(ExpressionEditor), new PropertyMetadata(null,SourceObjTypeChanged));

        /// <summary>
        /// Identifies the <see cref="T:NuSoft.ExpressionEditor.ExpressionEditor.SourceProps"/> dependency property. 
        /// </summary>
        public static readonly DependencyProperty SourcePropertiesProperty =
            DependencyProperty.Register("SourceProperties", typeof(SourcePropsCollection), typeof(ExpressionEditor), new PropertyMetadata(null,OnSourcePropertiesChanged));

        /// <summary>
        /// Identifies the <see cref="T:NuSoft.ExpressionEditor.ExpressionEditor.Parser"/> dependency property. 
        /// </summary>
        public static readonly DependencyProperty ParserProperty =
            DependencyProperty.Register("Parser", typeof(IParser), typeof(ExpressionEditor), new PropertyMetadata(null,OnParserChanged));

        /// <summary>
        /// Identifies the <see cref="T:NuSoft.ExpressionEditor.ExpressionEditor.SourceObj" /> dependency property. 
        /// </summary>
        public static readonly DependencyProperty SourceObjProperty =
            DependencyProperty.Register("SourceObj", typeof(object), typeof(ExpressionEditor), new PropertyMetadata(null,SourceObjChanged));

        /// <summary>
        /// Identifies the <see cref="T:NuSoft.ExpressionEditor.ExpressionEditor.ParseResult"/> dependency property. 
        /// </summary>
        public static readonly DependencyProperty ParseResultProperty =
            DependencyProperty.Register("ParseResult", typeof(object), typeof(ExpressionEditor), new PropertyMetadata(""));

        /// <summary>
        /// Identifies the <see cref="T:NuSoft.ExpressionEditor.ExpressionEditor.ExpressionText"/> dependency property. 
        /// </summary>
        public static readonly DependencyProperty ExpressionTextProperty =
            DependencyProperty.Register("ExpressionText", typeof(string), typeof(ExpressionEditor), new PropertyMetadata("",ExpressionTextChanged));

        /// <summary>
        /// Identifies the <see cref="T:NuSoft.ExpressionEditor.ExpressionEditor.HasError"/> dependency property. 
        /// </summary>
        public static readonly DependencyProperty HasErrorProperty =
            DependencyProperty.Register("HasError", typeof(bool), typeof(ExpressionEditor), new PropertyMetadata(false));

        /// <summary>
        /// Identifies the <see cref="T:NuSoft.ExpressionEditor.ExpressionEditor.ErrorInfo"/> dependency property. 
        /// </summary>
        public static readonly DependencyProperty ErrorInfoProperty =
            DependencyProperty.Register("ErrorInfo", typeof(string), typeof(ExpressionEditor), new PropertyMetadata(""));

        private ListView propertiesLv;

        private TextBox expressionTbx;

        private ListView methodsLv;

        private Grid propertiesGrid;

        private Button evaluateBtn;

        /// <summary>
        /// Initializes new instance of the <see cref="T:NuSoft.ExpressionEditor.ExpressionEditor"/> class. 
        /// </summary>
        public ExpressionEditor() : base()
        {
            this.SourceProperties = new SourcePropsCollection();
            this.SupportedMethods = new SupportedMethodsCollection();
        }

        static ExpressionEditor()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(ExpressionEditor), new FrameworkPropertyMetadata(typeof(ExpressionEditor)));
        }

        /// <summary>
        /// Gets whether expression validation failed. 
        /// </summary>
        public bool HasError
        {
            get
            {
                return (bool)this.GetValue(HasErrorProperty);
            }
            protected set
            {
                this.SetValue(HasErrorProperty, value);
            }
        }

        /// <summary>
        /// Gets validation error message. 
        /// </summary>
        public string ErrorInfo
        {
            get
            {
                return (string)this.GetValue(ErrorInfoProperty);
            }
            protected set
            {
                this.SetValue(ErrorInfoProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets supported methods collection. 
        /// </summary>
        public SupportedMethodsCollection SupportedMethods
        {
            get
            {
                return (SupportedMethodsCollection)this.GetValue(SupportedMethodsProperty);
            }
            set
            {
                this.SetValue(SupportedMethodsProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets source object type. 
        /// </summary>
        public Type SourceObjType
        {
            get
            {
                return (Type)this.GetValue(SourceObjTypeProperty);
            }
            set
            {
                this.SetValue(SourceObjTypeProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets source object propeties. 
        /// </summary>
        public SourcePropsCollection SourceProperties
        {
            get
            {
                return (SourcePropsCollection)this.GetValue(SourcePropertiesProperty);
            }
            set
            {
                this.SetValue(SourcePropertiesProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets expression parser. 
        /// </summary>
        public IParser Parser
        {
            get
            {
                return (IParser)this.GetValue(ParserProperty);
            }
            set
            {
                this.SetValue(ParserProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets source object. 
        /// </summary>
        public object SourceObj
        {
            get
            {
                return this.GetValue(SourceObjProperty);
            }
            set
            {
                this.SetValue(SourceObjProperty, value);
            }
        }

        /// <summary>
        /// Gets parse result
        /// </summary>
        public object ParseResult
        {
            get
            {
                return this.GetValue(ParseResultProperty);
            }
            protected set
            {
                this.SetValue(ParseResultProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets expression. 
        /// </summary>
        public string ExpressionText
        {
            get
            {
                return (string)this.GetValue(ExpressionTextProperty);
            }
            set
            {
                this.SetValue(ExpressionTextProperty, value);
            }
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            this.propertiesLv = this.GetTemplateChild("PropertiesList_PART") as ListView;
            if (this.propertiesLv != null)
            {
                this.propertiesLv.MouseDoubleClick += this.ListPartDoubleClick;
            }

            this.expressionTbx = this.GetTemplateChild("ExpressionText_PART") as TextBox;

            this.methodsLv = this.GetTemplateChild("MethodsList_PART") as ListView;
            if (this.methodsLv != null)
            {
                this.methodsLv.MouseDoubleClick += this.SupportedMethodsMouseDoubleClick;
            }
            this.propertiesGrid = this.GetTemplateChild("PropertyGrid_PART") as Grid;
            this.FillPropertyGrid();

            this.evaluateBtn = this.GetTemplateChild("EvaluateButton_PART") as Button;
            if (this.evaluateBtn != null)
            {
                this.evaluateBtn.Click += this.EvaluateButtonClick;
            }
            //new Button
        }

        /// <summary>
        /// Handles <see cref="T:NuSoft.ExpressionEditor.ExpressionEditor.ExpressionText"/> change. 
        /// </summary>
        private static void ExpressionTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d != null && d is ExpressionEditor)
            {
                ExpressionEditor ee = d as ExpressionEditor;
                ee.HasError = false;
                ee.ErrorInfo = "";
            }
        }

        /// <summary>
        /// Handles <see cref="T:NuSoft.ExpressionEditor.ExpressionEditor.SourceObj"/> change. 
        /// </summary>
        private static void SourceObjChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d != null && d is ExpressionEditor)
            {
                ExpressionEditor ee = d as ExpressionEditor;
                ee.FillPropertyGrid();
            }
        }

        /// <summary>
        /// Handles <see cref="T:NuSoft.ExpressionEditor.ExpressionEditor.SupportedMethods"/> change. 
        /// </summary>
        private static void SupportedMethodsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d != null && d is ExpressionEditor)
            {
                ExpressionEditor ee = d as ExpressionEditor;
                ee.UpdateSupportedMethods();
            }
        }

        /// <summary>
        /// Handles <see cref="T:NuSoft.ExpressionEditor.ExpressionEditor.Parser"/> change. 
        /// </summary>
        private static void OnParserChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d != null && d is ExpressionEditor)
            {
                ExpressionEditor ee = d as ExpressionEditor;
                ee.UpdateSupportedMethods();
            }
        }

        /// <summary>
        /// Handles <see cref="T:NuSoft.ExpressionEditor.ExpressionEditor.SourceProps"/> change. 
        /// </summary>
        private static void OnSourcePropertiesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d != null && d is ExpressionEditor)
            {
                ExpressionEditor ee = d as ExpressionEditor;
                ee.UpdateSourceProperties();
            }
        }

        /// <summary>
        /// Handles <see cref="T:NuSoft.ExpressionEditor.ExpressionEditor.SourceObjType"/> change. 
        /// </summary>
        private static void SourceObjTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d != null && d is ExpressionEditor)
            {
                ExpressionEditor ee = d as ExpressionEditor;
                ee.UpdateSourceProperties();
                if (ee.SourceObj == null)
                {
                    ee.SourceObj = Activator.CreateInstance(ee.SourceObjType);
                }
            }
        }

        /// <summary>
        /// Updates collection of Parsers supported methods. 
        /// </summary>
        private void UpdateSupportedMethods()
        {
            if (this.Parser == null || this.SupportedMethods == null)
            {
                return;
            }
            this.SupportedMethods.Clear();
            foreach (ISupportedMethod item in this.Parser.SupportedMethods)
            {
                this.SupportedMethods.Add(item);
            }
        }

        /// <summary>
        /// Updates collection of source properties. 
        /// </summary>
        /// <remarks>
        /// If <see cref="T:NuSoft.ExpressionEditor.ExpressionEditor.SourceObjType"/> or <see cref="T:NuSoft.ExpressionEditor.ExpressionEditor.SourceProperties"/> collection is null, nothing will happen. 
        /// </remarks>
        private void UpdateSourceProperties()
        {
            if (this.SourceObjType == null || this.SourceProperties == null)
            {
                return;
            }
            this.SourceProperties.Clear();
            foreach (string item in from prop in this.SourceObjType.GetProperties()
                                    where prop.GetGetMethod() != null &&
                                          prop.HasAttr<SourcePropertyAttribute>()
                                    select prop.Name)
            {
                this.SourceProperties.Add(item);
            }
        }

        /// <summary>
        /// Handles evaluation button click. 
        /// </summary>
        /// <remarks>
        /// Evaluates <see cref="T:NuSoft.ExpressionEditor.ExpressionEditor.ExpressionText"/> using <see cref="T:NuSoft.ExpressionEditor.ExpressionEditor.SourceObj"/>. 
        /// </remarks>
        void EvaluateButtonClick(object sender, RoutedEventArgs e)
        {
            if (this.Parser != null)
            {
                IParsingResult res = this.Parser.Parse(this.SourceObj, this.ExpressionText);
                this.ParseResult = res.Result;

                this.HasError = res.HasError;
                this.ErrorInfo = res.ErrorInfo;
            }
            else
            {
                this.HasError = true;
                this.ErrorInfo = "Parser not set.";
            }
        }
  
        /// <summary>
        /// Fills property grid. 
        /// </summary>
        /// <remarks>
        /// If no <see cref="T:NuSoft.ExpressionEditor.ExpressionEditor.SourceObjType"/> PropertyGrid_PART provided in template, nothing will happen.
        /// </remarks>
        private void FillPropertyGrid()
        {
            if (this.propertiesGrid == null)
            {
                return;
            }
            this.propertiesGrid.Children.Clear();
            this.propertiesGrid.RowDefinitions.Clear();
            if (this.SourceObj == null || this.SourceProperties == null)
            {
                return;
            }
            for (int i = 0; i < this.SourceProperties.Count; i++)
            {
                this.propertiesGrid.RowDefinitions.Add(new RowDefinition
                {
                    Height = GridLength.Auto
                });
                Label textBlock = new Label { Content = this.SourceProperties[i] };
                textBlock.SetValue(Grid.RowProperty, i);
                this.propertiesGrid.Children.Add(textBlock);
                TextBox textBox = new TextBox();
                textBox.SetValue(Grid.RowProperty, i);
                textBox.SetValue(Grid.ColumnProperty, 1);
                textBox.SetBinding(TextBox.TextProperty, new Binding(this.SourceProperties[i])
                {
                    Source = this.SourceObj
                });
                this.propertiesGrid.Children.Add(textBox);
            }
            this.propertiesGrid.RowDefinitions.Add(new RowDefinition());
        }

        /// <summary>
        /// Handles 
        /// </summary>
        void SupportedMethodsMouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            if (sender != null && sender is ListView)
            {
                ListView lv = sender as ListView;
                if (lv.SelectedItem != null && lv.SelectedItem is ISupportedMethod)
                {
                    ISupportedMethod sm = lv.SelectedItem as ISupportedMethod;
                    this.InsertText(sm.Insert);
                }
            }
        }
 
        /// <summary>
        /// Based on <see cref="T:System.Windows.Controls.TextBox.SelectionStart"/>, inserts text into <see cref="T:NuSoft.ExpressionEditor.ExpressionEditor.ExpressionText"/>. 
        /// </summary>
        /// <remarks>
        /// If no ExpressionText_PART provided in template, will just append at the and of <see cref="T:NuSoft.ExpressionEditor.ExpressionEditor.ExpressionText"/>. 
        /// </remarks>
        /// <param name="text">Text to be inserted. </param>
        private void InsertText(string text)
        {
            if (this.expressionTbx != null)
            {
                this.ExpressionText = this.ExpressionText.Insert(this.expressionTbx.SelectionStart, text);
                this.expressionTbx.SelectionStart += text.Length;
            }
            else
            {
                this.ExpressionText += text;
            }
        }

        void ListPartDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            if (sender != null && sender is ListView)
            {
                ListView lv = sender as ListView;
                if (lv.SelectedItem != null && lv.SelectedItem is string)
                {
                    this.InsertText(string.Format("${0}", lv.SelectedItem));
                }
            }
        }
    }
}